// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Logger from '../Logger';

const CAE_STREAM_DELIMITER_MAGICWORD = 0x5A5A;
const WEBSOCKET_OPEN_STATE = 1;
const HEART_BEAT_INTERVAL = 500;
const PACKAGE_HEADER_LENGTH = 8;
const MSG_TYPE_MAP = {
    cmdControl: 7,
    heartBeat: 8
};
const MSG_CMD_MAP = {
    start: '0',
    stop: '1',
    heartBeat: '2',
    reconnect: '4',
    pause: '5',
    recover: '6'
};

class SocketWorker {
    constructor() {
        this.ws = null;
        this.heartBeatTimer = null;
    }

    getCheckSum(msgType) {
        return (msgType + ((CAE_STREAM_DELIMITER_MAGICWORD >> 8) & 0xFF) + (CAE_STREAM_DELIMITER_MAGICWORD & 0xFF)) & 0xFF;
    }

    makeActionMsg(msgType, msgCmd, paramStr) {
        let msgBody = 'command=' + MSG_CMD_MAP[msgCmd];
        paramStr && (msgBody += ('&' + paramStr));

        const cmdBuf = new Uint8Array(PACKAGE_HEADER_LENGTH + msgBody.length);
        cmdBuf[0] = 90;
        cmdBuf[1] = 90;
        cmdBuf[2] = this.getCheckSum(MSG_TYPE_MAP[msgType]);
        cmdBuf[3] = MSG_TYPE_MAP[msgType];
        cmdBuf[4] = 0;
        cmdBuf[5] = 0;
        cmdBuf[6] = 0;
        cmdBuf[7] = msgBody.length;

        for (let i = 0, len = msgBody.length; i < len; i++) {
            cmdBuf[PACKAGE_HEADER_LENGTH + i] = msgBody.charCodeAt(i);
        }

        return cmdBuf.buffer;
    }

    startHeartbeat() {
        const buf = this.makeActionMsg('heartBeat', 'heartBeat');
        buf && this.ws.send(buf);
        this.heartBeatTimer && clearInterval(this.heartBeatTimer);
        this.heartBeatTimer = setInterval(() => {
            if (WEBSOCKET_OPEN_STATE === this.ws.readyState) {
                const buf = this.makeActionMsg('heartBeat', 'heartBeat');
                this.ws.send(buf);

                // 心跳发送数据数量超过maxCount则停止发送，避免异常断网等导致未及时收到响应导致数组过大。
                this.onHeartbeatSended && this.onHeartbeatSended(Date.now());
            } else {
                this.stopHeartbeat();
            }
        }, HEART_BEAT_INTERVAL);
    }

    stopHeartbeat() {
        clearInterval(this.heartBeatTimer);
        Logger.debug('Stop sending heartbeat.');
    }

    init(options) {
        this.ws = new WebSocket(options.protocol + '://' + options.connectURI);
        this.ws.binaryType = 'arraybuffer';

        this.ws.onopen = () => {
            this.onOpen && this.onOpen(this.ws.readyState);
            if (options.needHeatBeat) {
                this.startHeartbeat();
            }
        };

        this.ws.onmessage = evt => {
            this.onMessage && this.onMessage(evt.data);
        };

        this.ws.onerror = evt => {
            Logger.debug('ws error: ', evt);
            this.onError && this.onError();
        };

        this.ws.onclose = () => {
            this.onClose && this.onClose(this.ws.readyState);
        };
    }

    send(buf) {
        this.ws.send(buf);
    }

    processReq(req) {
        switch (req.cmd) {
        case 'initReq':
            this.init(req.options);
            break;
        case 'sendReq':
            this.send(req.data);
            break;
        case 'startHeartbeatReq':
            this.startHeartbeat();
            break;
        case 'stopHeartbeatReq':
            this.stopHeartbeat();
            break;
        case 'closeReq':
            this.destroy();
            break;
        case 'socketStateReq':
            this.onState(this.ws.readyState);
            break;
        default:
            Logger.debug('Unknown command socket worker received.');
        }
    }

    destroy() {
        this.ws && this.ws.close();
    }
}

if (typeof self !== 'undefined') {
    self.socket = new SocketWorker;
    self.socket.onOpen = state => {
        self.postMessage({cmd: 'openRsp', state: state});
    };

    self.socket.onError = () => {
        self.postMessage({cmd: 'errorRsp'});
    };
    
    self.socket.onMessage = data => {
        self.postMessage({cmd: 'recvRsp', buf: data, time: Date.now()});
    };

    self.socket.onHeartbeatSended = time => {
        self.postMessage({cmd: 'heartbeatRsp', time: time});
    };

    self.socket.onClose = state => {
        self.postMessage({cmd: 'closeRsp', state: state});
    };

    self.socket.onState = state => {
        self.postMessage({cmd: 'socketStateRsp', state});
    };

    self.addEventListener('message', evt => {
        const req = evt.data;
        self.socket.processReq(req);
    }, false);
}

export default SocketWorker;
